package com.griddroid.computations;

import java.io.Serializable;
import java.math.BigDecimal;

import com.griddroid.exceptions.NonCoherentComputationException;
import com.griddroid.exceptions.NotCalculatedComputationException;

/**
 * Provides an abstract class for every grid computation. Every grid task can be
 * represented as a computation. This computations have some common features,
 * like being split into pieces or merge many pieces in one. Also, as mostly all
 * grid computations deal with mathematics, they should know how to evaluate a
 * mathematical expression.
 * <p>
 * To extend the functionality of the grid in your own project, you should
 * firstly extend the Computation and implement your own functionality
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 * 
 */
public abstract class Computation implements Serializable
{

	private static final long serialVersionUID = -7554593653352964323L;

	/**
	 * Has the arithmetic result of the Computation, after it has been processed
	 */
	protected double result;
	/**
	 * Set to true whenever the Computation has been processed and the result
	 * has been set
	 */
	protected boolean calculated; // is set true in calculate

	/**
	 * Splits the Computation into the given number of parts.
	 * 
	 * @param parts
	 *            the number of parts to split the Computation into
	 * @return an array of smaller Computations, that result from the original
	 *         one
	 */
	public abstract Computation[] split(int parts);

	@Override
	public abstract String toString();

	/**
	 * Processes the Computations and saves the result. To access the result
	 * check {@link #getResult() getResult}
	 */
	public void calculate()
	{
		this.calculated = true;
	}

	/**
	 * Given a mathematical expression and the value for each variable in this
	 * expression, this method is responsible for calculating the arithmetic
	 * result. It uses the {@link MathEvaluator MathEvaluator} class
	 * 
	 * @param vars
	 *            The variables of the expression
	 * @param values
	 *            The values that will be assigned to the corresponding variable
	 * @param expression
	 *            A mathematical expression
	 * @return result of the expression, if we replace the variables of the
	 *         expression with the corresponding values. The return type is
	 *         double, but it can be extended to use {@link BigDecimal
	 *         BigDecimal} for bigger and more accurate results
	 */
	protected double evaluateExpression(String[] vars, double[] values, String expression)
	{
		MathEvaluator m = new MathEvaluator(expression);
		int i = 0;
		for (String var : vars)
		{
			m.addVariable(var, values[i]);
			i++;
		}
		Double result = m.getValue();
		return result;
	}

	/**
	 * Merges parts of the same Computation into a new one. Called like this:
	 * 
	 * 
	 * <code>//given a computation c and an array c_parts, all of which are from the same Computation
	 * c.merge(c_parts);//now c represents the merged results of each one of c_parts</code>
	 * 
	 * 
	 * @param parts
	 *            array with calculated parts from the same computation
	 * @throws NotCalculatedComputationException
	 *             one or more parts of the array has not been calculated
	 * @throws NonCoherentComputationException
	 *             one or more parts of the array do not belong to the
	 *             Computation to whom the others belong.
	 */
	public abstract void merge(Computation[] parts) throws NotCalculatedComputationException, NonCoherentComputationException;

	/**
	 * Returns the calculated result of the Computation
	 * 
	 * @return the arithmetic result of the Computation. It's a double value
	 * @throws NotCalculatedComputationException
	 *             {@link #calculate() calculate()} should be called in advance
	 * @see #result
	 */
	public double getResult() throws NotCalculatedComputationException
	{
		if (this.calculated)
		{
			return result;
		}
		else
			throw new NotCalculatedComputationException();
	}

	/**
	 * 
	 * @return true if {@link #calculate() calculate()} has been called
	 */
	public boolean isCalculated()
	{
		return calculated;
	}
}
